﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web;

namespace ESoft.Core.Util.Extensions
{
    public static class DictionaryExtensions
    {
        /// <summary>
        ///     强转换字典为其他类型的字典
        /// </summary>
        /// <typeparam name="TKeyOut">The type of the t key out.</typeparam>
        /// <typeparam name="TValOut">The type of the t value out.</typeparam>
        /// <param name="d">The d.</param>
        /// <returns>IDictionary&lt;TKeyOut, TValOut&gt;.</returns>
        public static IDictionary<TKeyOut, TValOut> ConvertTo<TKeyOut, TValOut>(this IDictionary d)
        {
            var result = new Dictionary<TKeyOut, TValOut>();
            foreach (DictionaryEntry v in d)
                result.Add((TKeyOut) v.Key, (TValOut) v.Value);
            return result;
        }

        /// <summary>
        ///     使用转换器对字典进行转换
        /// </summary>
        /// <typeparam name="TKeyOut">The type of the t key out.</typeparam>
        /// <typeparam name="TValOut">The type of the t value out.</typeparam>
        /// <param name="d">The d.</param>
        /// <param name="keyConverter">The key converter.</param>
        /// <param name="valConverter">The value converter.</param>
        /// <returns>IDictionary&lt;TKeyOut, TValOut&gt;.</returns>
        public static IDictionary<TKeyOut, TValOut> ConvertTo<TKeyOut, TValOut>(this IDictionary d,
            Func<object, TKeyOut> keyConverter, Func<object, TValOut> valConverter)
        {
            var result = new Dictionary<TKeyOut, TValOut>();
            foreach (DictionaryEntry v in d)
                result.Add(keyConverter(v.Key), valConverter(v.Value));
            return result;
        }

        /// <summary>
        ///     把字典转换为NameValue集合
        /// </summary>
        /// <param name="d">The d.</param>
        /// <returns>NameValueCollection.</returns>
        public static NameValueCollection ToNameValueCollection(this IDictionary<string, string> d)
        {
            var n = new NameValueCollection();
            foreach (var i in d)
                n.Add(i.Key, i.Value);

            return n;
        }

        /// <summary>
        /// 合并source所有的键值到destination
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TK">The type of the tk.</typeparam>
        /// <typeparam name="TV">The type of the tv.</typeparam>
        /// <param name="destination">The destination.</param>
        /// <param name="sources">The sources.</param>
        /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
        public static void MergeLeft<T, TK, TV>(this T destination, IEnumerable<IDictionary<TK, TV>> sources, bool overwrite = false)
            where T : IDictionary<TK, TV>
        {
            foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false))
            {
                destination[p.Key] = p.Value;
            }
        }

        /// <summary>
        /// 合并source所有的键值到destination
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TK">The type of the tk.</typeparam>
        /// <typeparam name="TV">The type of the tv.</typeparam>
        /// <param name="destination">The destination.</param>
        /// <param name="source">The source.</param>
        /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
        public static void MergeLeft<T, TK, TV>(this T destination, IDictionary<TK, TV> source, bool overwrite = false)
            where T : IDictionary<TK, TV>
        {
            destination.MergeLeft(new[] { source }, overwrite);
        }

        /// <summary>
        ///     通过key获取内容,无此项则返回默认值
        /// </summary>
        /// <typeparam name="TKey">The type of the t key.</typeparam>
        /// <typeparam name="TVal">The type of the t value.</typeparam>
        /// <param name="d">The d.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>TVal.</returns>
        public static TVal GetValue<TKey, TVal>(this IDictionary<TKey, TVal> d, TKey key,
            TVal defaultValue = default(TVal))
        {
            if (d.ContainsKey(key))
                return d[key];

            return defaultValue;
        }

        /// <summary>
        /// 通过key获取内容
        /// </summary>
        /// <typeparam name="TKey">The type of the t key.</typeparam>
        /// <typeparam name="TVal">The type of the t value.</typeparam>
        /// <param name="d">The d.</param>
        /// <param name="key">The key.</param>
        /// <returns>System.String.</returns>
        public static string GetValueAsString<TKey, TVal>(this IDictionary<TKey, TVal> d, TKey key)
        {
            if (d.ContainsKey(key))
                return d[key]?.ToString()??string.Empty;

            return string.Empty;
        }

        /// <summary>
        /// 根据key获取值,key没有或值为null时返回defaultValue
        /// </summary>
        /// <typeparam name="TKey">The type of the t key.</typeparam>
        /// <typeparam name="TVal">The type of the t value.</typeparam>
        /// <param name="d">The d.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>System.String.</returns>
        public static string GetValueAsString<TKey, TVal>(this IDictionary<TKey, TVal> d, TKey key, string defaultValue)
        {
            if (d.ContainsKey(key))
            {
                var value = d[key]?.ToString();
                if (!string.IsNullOrEmpty(value))
                    return value;
            }

            return defaultValue;
        }

        /// <summary>
        /// 判断是否存在key项，忽略key大小写问题
        /// </summary>
        /// <typeparam name="TValue">The type of the t value.</typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <returns><c>true</c> if [contains key ignore case] [the specified key]; otherwise, <c>false</c>.</returns>
        public static bool ContainsKeyIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key)
        {
            return dictionary.Keys.Contains(key, StringComparer.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// 根据key获取值，忽略key大小写问题
        /// </summary>
        /// <typeparam name="TValue">The type of the t value.</typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <returns>TValue.</returns>
        public static TValue GetValueIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key)
        {
            return dictionary.GetValueIgnoreCase(key, default(TValue));
        }

        /// <summary>
        /// 根据key获取值，忽略key大小写问题
        /// </summary>
        /// <typeparam name="TValue">The type of the t value.</typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>TValue.</returns>
        public static TValue GetValueIgnoreCase<TValue>(this IDictionary<string, TValue> dictionary, string key,
            TValue defaultValue)
        {
            key = dictionary.Keys.FirstOrDefault(i => i.Equals(key, StringComparison.InvariantCultureIgnoreCase));

            return key.IsNullOrWhiteSpace() == false
                ? dictionary[key]
                : defaultValue;
        }

        /// <summary>
        /// 字典转换为query string,例如:
        /// id=1&name=cs 
        /// </summary>
        /// <param name="d">The d.</param>
        /// <returns>System.String.</returns>
        public static string ToQueryString(this IDictionary<string, object> d)
        {
            if (!d.Any()) return "";
            var builder = new StringBuilder();
            foreach (var i in d)
            {
                builder.Append($"{HttpUtility.UrlEncode(i.Key)}={(i.Value == null ? string.Empty: HttpUtility.UrlEncode(i.Value.ToString()))}&");
            }

            return builder.ToString().TrimEnd('&');
        }

        public static void RemoveFromDictionary<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
            Func<KeyValuePair<TKey, TValue>, bool> removeCondition)
        {
            dictionary.RemoveFromDictionary((entry, innerCondition) =>
                {
                    return innerCondition(entry);
                },
                removeCondition);
        }

        /// <summary>
        /// Remove entries from dictionary that match the removeCondition.
        /// </summary>
        public static void RemoveFromDictionary<TKey, TValue, TState>(this IDictionary<TKey, TValue> dictionary, Func<KeyValuePair<TKey, TValue>, TState, bool> removeCondition, TState state)
        {
            if(dictionary==null) throw new ArgumentNullException(nameof(dictionary));
            if(removeCondition == null) throw new ArgumentNullException(nameof(removeCondition));

            // Because it is not possible to delete while enumerating, a copy of the keys must be taken. Use the size of the dictionary as an upper bound
            // to avoid creating more than one copy of the keys.
            int removeCount = 0;
            TKey[] keys = new TKey[dictionary.Count];
            foreach (var entry in dictionary)
            {
                if (removeCondition(entry, state))
                {
                    keys[removeCount] = entry.Key;
                    removeCount++;
                }
            }
            for (int i = 0; i < removeCount; i++)
            {
                dictionary.Remove(keys[i]);
            }
        }

        /// <summary>
        /// 根据key获取对应的
        /// 没有忽略key大小写问题
        /// 为假时则返回默认值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        /// <exception cref="System.ArgumentNullException">dictionary</exception>
        public static bool TryGetValue<T>(this IDictionary<string, object> dictionary, string key, out T value)
        {
            if (dictionary == null) throw new ArgumentNullException(nameof(dictionary));

            object valueObj;
            if (dictionary.TryGetValue(key, out valueObj))
            {
                if (valueObj is T)
                {
                    value = (T)valueObj;
                    return true;
                }
            }

            value = default(T);
            return false;
        }

        /// <summary>
        /// 通过前缀获取项
        /// prefix. 或 prefix[
        /// </summary>
        /// <typeparam name="TValue">The type of the t value.</typeparam>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="prefix">The prefix.</param>
        /// <returns>IEnumerable&lt;KeyValuePair&lt;System.String, TValue&gt;&gt;.</returns>
        /// <exception cref="System.ArgumentNullException">
        /// dictionary
        /// or
        /// prefix
        /// </exception>
        public static IEnumerable<KeyValuePair<string, TValue>> FindKeysWithPrefix<TValue>(this IDictionary<string, TValue> dictionary, string prefix)
        {
            if (dictionary == null) throw new ArgumentNullException(nameof(dictionary));
            if (prefix == null) throw new ArgumentNullException(nameof(prefix));

            TValue exactMatchValue;
            if (dictionary.TryGetValue(prefix, out exactMatchValue))
            {
                yield return new KeyValuePair<string, TValue>(prefix, exactMatchValue);
            }

            foreach (var entry in dictionary)
            {
                string key = entry.Key;

                if (key.Length <= prefix.Length)
                {
                    continue;
                }

                if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                // Everything is prefixed by the empty string
                if (prefix.Length == 0)
                {
                    yield return entry;
                }
                else
                {
                    char charAfterPrefix = key[prefix.Length];
                    switch (charAfterPrefix)
                    {
                        case '[':
                        case '.':
                            yield return entry;
                            break;
                    }
                }
            }
        }
    }
}